/*
 * Alloy Analyzer
 * Copyright (c) 2002 Massachusetts Institute of Technology
 *
 * The Alloy Analyzer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Alloy Analyzer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Alloy Analyzer; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package alloy.util;
import java.util.Iterator;

/**
 * Fixed-size multidimensional array of arbitrary dimension,
 * indexed by an array of indices.  Useful when you need multidimensional
 * arrays whose dimension is determined at runtime.
 * <p>
 * The data is stored in a flat array, in such a way that if you move through
 * this flat array from left to right, and print the multi-dimensional
 * indices of the elements stored in the cells you're seeing,
 * the <em>last</em> index varies the fastest.  You can get the flat
 * array by calling {@link #getFlatData}.
 * <p>
 * @see MultiDimIter
 */
public final class MultiDimArray implements Cloneable {
    /** The size of each dimension. */
    private int[] _dims;
    /** For each dimension, the size of the flat array block dedicated to one
    unit of that dimension.  Equal to the product of values of subsequent
    dimensions. */
    private int[] _dimSize;
    /** The data, stored as a flat array. {@link #inds2flat} returns the
    place in this array occupied by the element with the given indices. */
    private Object[] _data;

    /**
     * Construct a fixed-size multi-dimensional array with the given dimensions.
     * Allocate the space for the array.
     * @param dims_ Size of each dimension.  This array is not copied so must
     *              stay constant. */
    public MultiDimArray(int[] dims_) {
    this._dims = dims_;
    this._dimSize = new int[dims_.length];
    int flatSize = 1;
    int dsize = 1;
    for (int i=dims_.length-1; i>=0; i--) {
        _dimSize[i] = dsize;
        dsize *= dims_[i];
        flatSize *= dims_[i];
    }
    _data = new Object[flatSize];
    }

    public MultiDimArray(int a_) { this(new int[]{a_}); }
    public MultiDimArray(int a_, int b_) { this(new int[]{a_,b_}); }
    public MultiDimArray(int a_, int b_, int c_) { this(new int[]{a_,b_,c_}); }

    /**
     * Construct a copy of this {@link MultiDimArray}.
     * The new array contains references to the same
     * data, i.e. the data itself is not cloned.
     */
    public MultiDimArray(MultiDimArray src_) {
    this._dims = (int[])src_._dims.clone();
    this._dimSize = (int[])src_._dimSize.clone();
    this._data = (Object[])src_._data.clone();
    }

    /**
     * Construct a copy of this {@link MultiDimArray}.
     * The new array contains references to the same
     * data, i.e. the data itself is not cloned.
     */
    public Object clone() {
    MultiDimArray newCopy = null;
    try { newCopy = (MultiDimArray)super.clone(); }
    catch (CloneNotSupportedException e_) { }
    newCopy._dims = (int[])_dims.clone();
    newCopy._dimSize = (int[])_dimSize.clone();
    newCopy._data = (Object[])_data.clone();
    return newCopy;
    }


    /** Return the dimension sizes.  <em>Note:</em> returned array must
    not be modified! */
    public int[] getDims() { return _dims; }

    /**
     * Return the size of each dimension of the array.  The size of the
     * last dimension is 1.  The returned array must not be modified.
     */
    public int[] getDimSize() { return _dimSize; }

    /** Return the flat index of the element corresponding to the given indices. */
    public int inds2flat(int[] inds_) {
        Dbg.chk(inds_.length == _dims.length);
        int flatInd = 0;
        for (int i=0; i<inds_.length; i++) {
            //if (!(inds_[i] >= 0 && inds_[i] < _dims[i])) {
            //    System.out.println("inds=" + Util.str(inds_) + " dims=" + Util.str(_dims));
            //}
            Dbg.chk(inds_[i] >= 0 && inds_[i] < _dims[i]);
            flatInd += (inds_[i] * _dimSize[i]);
        }
        return flatInd;
    }

    /**
     * Return the flat index of the element corresponding to the given indices
     * in a {@link MultiDimArray} of given dimensions.
     */
    public static int inds2flat(int[] dims_, int[] inds_) {
    Dbg.chk(dims_.length == inds_.length);
    int dsize = 1;
    int flatInd = 0;
    for (int i=dims_.length-1; i>=0; i--) {
        flatInd += (inds_[i] * dsize);
        dsize *= dims_[i];
    }
    return flatInd;
    }

    /** Return an iterator over array indices.  Each {@link Iterator#next} call
    returns an int[] of indices of new elements.
    @see MultiDimIter */
    public Iterator indices() { return new MultiDimIter(_dims); }

    public void setFlat(int flatInd_, Object val_) { _data[flatInd_] = val_; }

    /** Record an object with given indices. */
    public void put(int[] inds_, Object val_) { _data[inds2flat(inds_)] = val_; }
    /** Return an object with given indices. */
    public Object get(int[] inds_) { return _data[inds2flat(inds_)]; }

    public Object get(int i) { return get(new int[]{i}); }
    public Object get(int i, int j) { return get(new int[]{i,j}); }
    public Object get(int i, int j, int k) { return get(new int[]{i,j,k}); }

    public void put(int i, Object val_) { put(new int[]{i}, val_); }
    public void put(int i, int j, Object val_) { put(new int[]{i,j}, val_); }
    public void put(int i, int j, int k, Object val_) { put(new int[]{i,j,k}, val_); }

    public int getFlatSize() { return _data.length; }
    public static int getFlatSize(int[] dims_) {
    int dsize = 1;
    for (int i=0; i<dims_.length; i++)
        dsize *= dims_[i];
    return dsize;
    }

    public void putFlat(int ind_, Object val_) { _data[ind_] = val_; }
    public Object getFlat(int ind_) { return _data[ind_]; }
    public Object[] getFlatData() { return _data; }
    /** Return data in array with runtime type of a */
    //    public Object[] getFlatData(Object[] a) { return Arrays.asList(_data).toArray(a); }

    public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append("{");
    int[] curInd = new int[_dims.length];
    _toStringHelper(sb, 0, curInd);
    sb.append("}");
    return sb.toString();
    }

    private void _toStringHelper(StringBuffer sb_, int dim_, int[] curInd_) {
    sb_.append("[");
    boolean isLastDim = (dim_ == _dims.length-1);
    for (int i=0; i<_dims[dim_]; i++) {
        curInd_[dim_] = i;
        if (isLastDim) {
        Object obj = get(curInd_);
        sb_.append(obj==null ? "null" : obj.toString());
        } else
        _toStringHelper(sb_, dim_+1, curInd_);
        if (i < _dims[dim_]-1)
        sb_.append(",");
    }
    sb_.append("]");
    }
}  // class MultiDimArray

